﻿//IT eBooks API Info Library. Simple .Net library to access the IT eBooks API Info http://it-ebooks-api.info/
//Copyright (C) 2014 Italo Pessoa <italo.pessoa@hotmail.com>

//This file is part of the program IT eBooks API Info Library.

//IT eBooks API Info Library is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using ITeBooks.API.Library.Model;
using Newtonsoft.Json.Linq;
using System.Net;
using System.IO;
using ITeBooks.API.Library.Interface;
using ITeBooks.API.Library.Exception;
using System.Drawing;
using System.Configuration;

namespace ITeBooks.API.Library.Converter
{
    /// <summary>
    /// Class to convert json in object
    /// </summary>
    public class JSONEBookConverter : IQuery<QueryResult>
    {
        #region Private Members

        private EBookQuery _query;
        private const string DEFAULT_SEARCH_TEMPLATE = "{0}search/{1}";
        private const string SEARCH_WITH_PAGE_TEMPLATE = "{0}search/{1}/page/{2}";
        private const string SEARCH_BY_ID_TEMPLATE = "{0}book/{1}";
        private readonly string RESULT_JSON;
        private bool _loadImages;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new converter
        /// </summary>
        public JSONEBookConverter()
        {
        }

        /// <summary>
        /// Create a new converter
        /// </summary>
        /// <param name="resultJson">json with the books values</param>
        /// <param name="loadImages">If true the library load the images in a object</param>
        public JSONEBookConverter(string resultJson, bool loadImages)
        {
            this.RESULT_JSON = resultJson;
            this._loadImages = loadImages;
        }

        /// <summary>
        /// Create a new converter
        /// </summary>
        /// <param name="query">Query</param>
        public JSONEBookConverter(EBookQuery query)
        {
            this._query = query;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Query to execute
        /// </summary>
        public EBookQuery Query
        {
            get { return this._query; }
            set { this._query = value; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Prepare eBooks to view
        /// </summary>
        /// <param name="jTokenBook">ebook Token</param>
        /// <returns><code>ITeBooks.API.Library.Model.EBook</code></returns>
        private EBook GetPresentationBook(JToken jTokenBook)
        {
            EBook eb;

            long id = (long)jTokenBook[JSONProperties.ID_KEY];
            string title = (string)jTokenBook[JSONProperties.TITLE_KEY];
            string subTitle = (string)jTokenBook[JSONProperties.SUBTITLE_KEY];
            string description = (string)jTokenBook[JSONProperties.DESCRIPTION_KEY];
            string imageLink = (string)jTokenBook[JSONProperties.IMAGE_KEY];
            long isbn = (long)jTokenBook[JSONProperties.ISBN_KEY];

            if ((this._query != null && this._query.LoadImagens) || this._loadImages)
            {
                eb = new EBook(id, title, subTitle, description, this.LoadBookImage(imageLink), isbn);
            }
            else
            {
                eb = new EBook(id, title, subTitle, description, imageLink, isbn);
            }

            return eb;
        }

        /// <summary>
        /// Prepare eBook with details
        /// </summary>
        /// <param name="jTokenBook">eBook token</param>
        /// <returns><code>ITeBooks.API.Library.Model.EBook</code></returns>
        private EBook GetCompleteBook(JToken jTokenBook)
        {
            EBook eb;

            long id = (long)jTokenBook[JSONProperties.ID_KEY];
            string title = (string)jTokenBook[JSONProperties.TITLE_KEY];
            string subTitle = (string)jTokenBook[JSONProperties.SUBTITLE_KEY];
            string description = (string)jTokenBook[JSONProperties.DESCRIPTION_KEY];
            string imageLink = (string)jTokenBook[JSONProperties.IMAGE_KEY];
            long isbn = (long)jTokenBook[JSONProperties.ISBN_KEY.ToUpper()];
            int year = (int)jTokenBook[JSONProperties.YEAR_KEY];
            int pages = (int)jTokenBook[JSONProperties.PAGE_KEY];
            string publisher = (string)jTokenBook[JSONProperties.PUBLISHER_KEY];
            string download = (string)jTokenBook[JSONProperties.DOWNLOAD_LINK_KEY];
            string[] authors = ((string)jTokenBook[JSONProperties.AUTHOR_KEY]).Split(',');

            if ((this._query != null && this._query.LoadImagens) || this._loadImages)
            {
                eb = new EBook(id, title, subTitle, description, this.LoadBookImage(imageLink), isbn, authors, year, pages, publisher, download);
            }
            else
            {
                eb = new EBook(id, title, subTitle, description, imageLink, isbn, authors, year, pages, publisher, download);
            }

            return eb;
        }

        /// <summary>
        /// Load image from url
        /// </summary>
        /// <param name="url"></param>
        /// <returns><code>System.Drawing.Image</code></returns>
        private Image LoadBookImage(string url)
        {
            return this.GetImageFromUrl(url);
        }

        /// <summary>
        /// Execute the query
        /// </summary>
        /// <returns>eBook token</returns>
        private JToken GetBooks()
        {
            if (String.IsNullOrEmpty(this.RESULT_JSON))
            {
                string apiLinkValue = String.IsNullOrEmpty(ConfigurationManager.AppSettings[JSONProperties.API_LINK_KEY]) ? JSONProperties.API_LINK_VALUE : ConfigurationManager.AppSettings[JSONProperties.API_LINK_KEY];

                HttpWebRequest request;
                if (this._query.Page.HasValue)
                {
                    string url = String.Format(SEARCH_WITH_PAGE_TEMPLATE, apiLinkValue, this._query.Query, this._query.Page);
                    request = (HttpWebRequest)WebRequest.Create(url);
                }
                else
                {
                    string url = String.Format(DEFAULT_SEARCH_TEMPLATE, apiLinkValue, this._query.Query);
                    request = (HttpWebRequest)WebRequest.Create(url);
                }

                //TODO: tratar System.Net.WebException
                var response = request.GetResponse();

                //// Obtain a 'Stream' object associated with the response object.
                Stream ReceiveStream = response.GetResponseStream();

                Encoding encode = System.Text.Encoding.GetEncoding("utf-8");

                //// Pipe the stream to a higher level stream reader with the required encoding format. 
                StreamReader readStream = new StreamReader(ReceiveStream, encode);

                JObject jObject = JObject.Parse(readStream.ReadToEnd());
                JToken root = (JToken)jObject;

                return root;
            }
            else
            {
                JObject jObject = JObject.Parse(this.RESULT_JSON);
                JToken root = (JToken)jObject;

                return root;
            }

        }

        /// <summary>
        /// Execute the query
        /// </summary>
        /// <returns></returns>
        private JToken GetBook()
        {
            if (String.IsNullOrEmpty(this.RESULT_JSON))
            {
                HttpWebRequest request;
                string url = String.Format(SEARCH_BY_ID_TEMPLATE, System.Configuration.ConfigurationManager.AppSettings[JSONProperties.API_LINK_KEY], this._query.BookID);
                request = (HttpWebRequest)WebRequest.Create(url);

                var response = request.GetResponse();

                //// Obtain a 'Stream' object associated with the response object.
                Stream ReceiveStream = response.GetResponseStream();

                Encoding encode = System.Text.Encoding.GetEncoding("utf-8");

                //// Pipe the stream to a higher level stream reader with the required encoding format. 
                StreamReader readStream = new StreamReader(ReceiveStream, encode);

                JObject jObject = JObject.Parse(readStream.ReadToEnd());

                JToken root = (JToken)jObject;

                return root;

            }
            else
            {
                JObject jObject = JObject.Parse(this.RESULT_JSON);
                JToken root = (JToken)jObject;

                return root;
            }
        }

        /// <summary>
        /// Get Image from url
        /// </summary>
        /// <param name="url">Image url</param>
        /// <returns>Image object</returns>
        private Image GetImageFromUrl(string url)
        {
            HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);

            using (HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse())
            {
                using (Stream stream = httpWebReponse.GetResponseStream())
                {
                    return Image.FromStream(stream);
                }
            }
        }

        /// <summary>
        /// Validate the query
        /// </summary>
        /// <exception cref="ITeBooks.API.Library.Exception.EBookQueryException"></exception>
        private void ValidateQuery()
        {
            if (this._query == null && String.IsNullOrEmpty(this.RESULT_JSON))
            {
                throw new EBookQueryException("Query not found!", new System.Exception("The query is null!"));
            }
            else if (this._query != null && !this._query.BookID.HasValue && String.IsNullOrEmpty(this._query.Query) && String.IsNullOrEmpty(this.RESULT_JSON))
            {
                throw new EBookQueryException("Query not found!", new System.Exception("Set the query or book id parameter!"));
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Find the eBooks by the query
        /// </summary>
        /// <returns>QueryResult</returns>
        /// <exception cref="ITeBooks.API.Library.Exception.EBookException"></exception>
        public QueryResult Find()
        {
            ValidateQuery();
            
            JToken root = this.GetBooks();

            string error = (string)root[JSONProperties.ERROR_KEY];
            int total = (int)root[JSONProperties.TOTAL_KEY];

            if (!error.Equals("0"))
            {
                throw new EBookException(error);
            }

            if (total <= 0)
            {
                return new QueryResult(error);
            }

            double time = (double)root[JSONProperties.TIME_KEY];

            int page = (int)root[JSONProperties.PAGE_KEY];

            JArray eBookArray = JArray.Parse(root[JSONProperties.BOOKS_KEY].ToString());

            EBook[] eBooks = new EBook[eBookArray.Count];

            for (int i = 0; i < eBookArray.Count; i++)
            {
                JToken token = eBookArray[i];
                eBooks[i] = GetPresentationBook(token);
            }

            QueryResult result = new QueryResult(error, time, page, total, eBooks);
            return result;
        }

        /// <summary>
        /// Find eBook by ID
        /// </summary>
        /// <returns>QueryResult</returns>
        /// <exception cref="ITeBooks.API.Library.Exception.EBookException"></exception>
        public QueryResult FindByID()
        {
            ValidateQuery();

            JToken root = this.GetBook();

            string error = (string)root[JSONProperties.ERROR_KEY];

            if (!error.Equals("0"))
            {
                throw new EBookException(error);
            }

            double time = (double)root[JSONProperties.TIME_KEY];

            EBook eBook = this.GetCompleteBook(root);

            QueryResult result = new QueryResult(error, time, eBook);
            return result;
        }

        #endregion
    }
}
